home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Ian & Stuart's Australian Mac 1
/
Ian and Stuart's One (Australia).iso
/
Australasian Legends
/
Commercial
/
Rainbow Hill
/
MacDOS™ 2.0.0
/
filters
/
filter projects
/
common pipe sources
/
pipe.c
next >
Wrap
Text File
|
1994-05-26
|
18KB
|
569 lines
/* pipe.c Pipe handling
*
* Released MacDOS 1.0.1a on 94/01/31
* 94/03/31 File created
* 94/05/26 First version completed
*
*--------------------------------------------------------------------------------------------------
* Copyright © 1994 by Rainbow Hill Pty Ltd.
*
* This program is free software; you can redistribute it and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <Errors.h>
#include <GestaltEqu.h>
#include <Processes.h>
#include <PPCToolBox.h>
#include <Events.h>
#include "pipe.h"
/*
* Before doing anything else, we hide from MacDOS what should only be used within filter
* applications and from non-MacDOS applications what should only be used by MacDOS.
* Very defensive!
*/
#ifdef __MacDOS__
# define onlyMDOS
# define notMDOS static
#else
# define onlyMDOS static
# define notMDOS
# endif
/*-------------------------------------------------------- type, constant, and macros definitions */
#define closedPort 0 /* because PPC port numbers are always > 0 */
/*
* StringAppend_M appends a P-string to another. It appears a bit more complicated than necessary
* because it avoids referencing the arguments more than once.
*/
#define StringAppend_M(dst, src) { \
unsigned char *ds0, *ds, *sr; \
short le; \
ds0 = (dst); /* point to the beginning of dst */ \
sr = (src); /* point to the beginning of src */ \
ds = ds0 + *ds0 + 1; /* point to immediately after the end of dst */ \
le = *sr++; /* get the length of src and skip it */ \
*ds0 += le; /* update the length of dst */ \
while (le-- > 0) *ds++ = *sr++; /* append the content of src to dst */ \
}
/*
* StringCopy_M copies the contents of one P-string to another P-string. Note that it refers
* to the arguments only once.
*/
#define StringCopy_M(dst, src) { \
unsigned char *ds, *sr; \
short le; \
ds = (dst); \
sr = (src); \
le = sr[0] + 1; \
while (le-- > 0) *ds++ = *sr++; \
}
/*------------------------------------------------------------------- static variable definitions */
static pipeParmsFun_t *processParms = nil; /* call back function to save filter parameters */
static PPCPortRefNum port = closedPort; /* PPC port number */
static unsigned char nextFilterID; /* ID of the next filter downstream */
/*------------------------------------------------------------------ static function declarations */
static OSErr ProcessMess(pipeMessType_t messType, unsigned char *buff);
static OSErr StartOneSession(pipe_t *p);
/*---------------------------------------------------------------------------- exported variables */
onlyMDOS Str32 pipeName = ""; /* name of the filter application/process */
onlyMDOS pipe_t pipeIn = { pipeNoSession }; /* incoming pipe */
onlyMDOS pipe_t pipeOut = { pipeNoSession };/* outgoing pipe */
onlyMDOS unsigned char pipeCurrentSeq = 0; /* last sequence number received from upstream */
onlyMDOS unsigned char pipeFilterID = pipeMacDOSID;/* ID of this filter */
/**************************************************************************** PipeAcceptInSession */
onlyMDOS OSErr PipeAcceptInSession(void) {
OSErr retVal;
static PPCInformPBRec informPB; /* static: we might return before completing PPCInform */
Boolean userCancelled;
/* check that the session is not already started */
if (pipeIn.session != pipeNoSession) {
retVal = noErr;
}
else {
/* set up the parameter block to automatically accept sessions from local processes */
informPB.ioCompletion = nil;
informPB.portRefNum = port;
informPB.autoAccept = true;
informPB.portName = nil;
informPB.locationName = nil;
informPB.userName = nil;
/* accept any session (hopefully it will be from either MacDOS or a proper filter!) */
retVal = PPCInform(&informPB, true);
if (retVal == noErr) {
PipeWaitOnCond_M(informPB.ioResult == pipePpcNotDone, userCancelled);
if (userCancelled) {
retVal = userCanceledErr;
}
else {
/* extract and process the information from the message */
retVal = informPB.ioResult;
if (retVal == noErr) {
pipeFilterID = informPB.userData; /* automatic conversion: long -> char */
/* set up the incoming pipe */
pipeIn.session = informPB.sessRefNum;
pipeCurrentSeq = 0;
retVal = StartOneSession(&pipeIn);
}
}
}
}
return (retVal);
} /* PipeAcceptInSession */
/********************************************************************************** PipeCheckQuit */
Boolean PipeCheckQuit(void) {
EventRecord keyboardPollEvent;
return (
(
WaitNextEvent(keyDownMask, &keyboardPollEvent, 0L, nil)
&&
PipeIsBreak_M(keyboardPollEvent)
)
? true
: false
);
} /* PipeCheckQuit */
/************************************************************************************ PipeCleanUp */
onlyMDOS void PipeCleanUp(void) {
PPCClosePBRec closePB;
/* check that the port is open */
if (port != closedPort) {
/* close the port */
closePB.ioCompletion = nil;
closePB.portRefNum = port;
(void)PPCClose(&closePB, false);
/* in any case, reset the port number (better safe than sorry!) */
port = closedPort;
}
/* Unnecessary, but it requires little effort. Let's keep everything tidy. */
pipeIn.session = pipeNoSession;
pipeOut.session = pipeNoSession;
} /* PipeCleanUp */
/*************************************************************************************** PipeInit */
notMDOS void PipeInit(
pipeParmsFun_t *pFun /* --> call back function to save filter parameters */
) {
OSErr err;
long ppcAttributes;
/* install the function to handle the filter parameters */
processParms = pFun;
/* check that the PPC toolbox is available */
err = Gestalt(gestaltPPCToolboxAttr, &ppcAttributes);
if (err == noErr) {
/* check whether the PPC was initialised and if not, do it */
if ((ppcAttributes & gestaltPPCSupportsRealTime) == 0) err = PPCInit();
if (err == noErr) {
/* open the PPC port */
err = PipeOpen();
if (err == noErr) {
/* wait for the process upstream to start a session */
err = PipeAcceptInSession();
}
}
}
/*
* If an error occurs at this stage, we can only quit the filter.
* In order to keep the ExitToShell call in a single place, we force the quit by
* by attempting to report an error to MacDOS. That function will fail because
* it will find the incoming pipe closed.
*/
if (err != noErr) {
PipeReportError(err, nil);
/* you will never come back here, because PipeReportError never returns */
}
} /* PipeInit */
/*************************************************************************************** PipeOpen */
onlyMDOS OSErr PipeOpen(void) {
OSErr retVal;
PPCOpenPBRec openPB;
PPCPortRec portRec;
LocationNameRec locRec;
short k;
/* To obtain the file name of this filter application, so that we ... */
short apRefNum; /* ... can name this port like ... */
Handle apParam; /* ... the application file. */
/* check that the port is not already open */
if (port != closedPort) {
retVal = pipeDoubleInitErr;
}
else {
/* initialise the port name structure with the name of the filter application */
GetAppParms(pipeName, &apRefNum, &apParam);
portRec.nameScript = *(short *)*(GetString(pipeScriptStrNum));
StringCopy_M(portRec.name, pipeName);
portRec.portKindSelector = ppcByString;
StringCopy_M(portRec.u.portTypeStr, pipeType);
/* initialise the location structure to point to the local Mac */
locRec.locationKindSelector = ppcNoLocation;
/* initialise the parameter block to open the port */
openPB.ioCompletion = nil;
openPB.serviceType = ppcServiceRealTime;
openPB.resFlag = 0;
openPB.portName = &portRec;
openPB.locationName = &locRec;
openPB.networkVisible = false;
openPB.portRefNum = closedPort; /* a bit of an overkill... */
/* open the port and save its value in the global variable */
retVal = PPCOpen(&openPB, false);
if (retVal == noErr) {
port = openPB.portRefNum;
pipeIn.session = pipeNoSession;
pipeOut.session = pipeNoSession;
}
}
return (retVal);
} /* PipeOpen */
/*************************************************************************************** PipePoll */
notMDOS void PipePoll(
unsigned char *inBuff /* --> pointer to the message buffer (a P-string) */
) {
OSErr err;
pipe_t *pip;
long userData;
unsigned char messType;
unsigned char srcFilter;
unsigned char dstFilter;
unsigned char seq;
Boolean fromUp;
/* poll the pipes as long as no error occurs */
do {
/* do one poll */
err = PipePollOnce(&pip, &userData, inBuff);
if (err == noErr) {
/* Determine whether the message is from upstream. We will need it later. */
fromUp = (pip == &pipeIn) ? true : false;
/* We have received a message. Break down the userData field into its components. */
PipeDecUserData_M(userData, messType, srcFilter, dstFilter, seq);
/* check whether the message is for us */
if (dstFilter != pipeFilterID) {
/* The message is for somebody else. Forward it to the other pipe. */
err = PipeStartWrite((fromUp) ? &pipeOut : &pipeIn, userData, inBuff);
}
else {
/* The message is for us. Ensure that it is not an error message. */
if (messType == pipeErrMess) {
/* An error message addressed to us? Filters cannot accept error messages. */
err = pipeFilterRespErr;
}
else if (fromUp == false) {
/* A message from downstream addressed to us? This is not right for filters. */
err = pipeUpstreamErr;
}
else {
/* This is a proper message from upstream. Process it. */
pipeCurrentSeq = seq;
err = ProcessMess(messType, inBuff); /* err == pipeReturnE: data message */
} /* we did receive what we can handle */
} /* the message was for us */
} /* PipePollOnce was successful */
} while (err == noErr || err == pipePpcNotDone);
/*
* If we arrive here, err contains an error code. Return successfully only if we broke out
* of the loop because we received a proper data message. Otherwise, try to report the error
* to MacDOS but only if the error concerns the outgoing pipe. If the error comes from the
* incoming pipe, there is not much point in attempting to send a message to it.
*
* In any case, we want to call PipeReportError because we don't want to use ExitToShell
* in more than one place.
*/
if (err != pipeReturnE) {
if (fromUp) pipeIn.session = pipeNoSession;
PipeReportError(err, nil);
/* you will never come back here, because PipeReportError never returns */
}
} /* PipePoll */
/*********************************************************************************** PipePollOnce */
onlyMDOS OSErr PipePollOnce(pipe_t **pip, long *userData, unsigned char *buff) {
OSErr retVal;
/* poll the outgoing pipe first, so that error messages are handled first */
if (pipeOut.session != pipeNoSession) {
*pip = &pipeOut;
retVal = pipeOut.readPB.ioResult;
}
else {
retVal = pipePpcNotDone;
}
/* if the output pipe is not there or does not hold any message, poll the input pipe */
if (retVal == pipePpcNotDone && pipeIn.session != pipeNoSession) {
*pip = &pipeIn;
retVal = pipeIn.readPB.ioResult;
}
/* check whether one of the two PPC Reads has completed successfully */
if (retVal == noErr) {
/* A PPC read was successful. Return the message. */
*userData = (**pip).readPB.userData;
StringCopy_M(buff, (**pip).readBuff);
/* start a new PPC read */
retVal = PPCRead(&(**pip).readPB, true);
}
else if (retVal == pipePpcNotDone) {
/* Nothing this time. Check whether the user wants to abort the command. */
if (PipeCheckQuit() == true) retVal = userCanceledErr;
}
return (retVal);
} /* PipePollOnce */
/******************************************************************************** PipeReportError */
notMDOS void PipeReportError(
OSErr errCode, /* --> error code */
unsigned char *errInClear /* --> error message in clear */
) {
OSErr err;
long userData;
unsigned char buff[pipeSize];
pipe_t *pip;
/* only attempt to send the message to MacDOS if the incoming pipe is open */
if (pipeIn.session != pipeNoSession) {
/* prepare the message to report the error to MacDOS */
PipeEncErrUserData_M(errCode, userData, buff);
if (errInClear != nil && errInClear[0] > 0) {
if ((short)errInClear[0] + (short)buff[0] >= pipeSize)
errInClear[0] = pipeSize - buff[0] - 1;
StringAppend_M(buff, errInClear);
}
/* send the error message to the incoming pipe */
err = PipeStartWrite(&pipeIn, userData, buff);
if (err == noErr) {
/* keep polling the incoming pipe (while ignoring all messages) until it fails */
do {
err = pipeIn.readPB.ioResult;
if (err == noErr) {
err = PPCRead(&pipeIn.readPB, true);
if (err == noErr) err = pipeIn.readPB.ioResult;
}
if (err == pipePpcNotDone && PipeCheckQuit() == true)
err = userCanceledErr;
} while (err == pipePpcNotDone);
}
}
/* close the PPC port and quit */
PipeCleanUp();
ExitToShell();
} /* PipeReportError */
/*********************************************************************************** PipeSendData */
void PipeSendData(
unsigned char *buff /* --> pointer to the message buffer (a P-string) */
) {
OSErr err;
long userData;
/* encode the userData field of the message */
PipeEncUserData_M(nextFilterID, pipeDataMess, userData);
/* send the message to the outgoing pipe */
err = PipeStartWrite(&pipeOut, userData, buff);
if (err != noErr) {
PipeReportError(err, nil);
/* you will never come back here, because PipeReportError never returns */
}
} /* PipeSendData */
/*************************************************************************** PipeStartNextSession */
onlyMDOS OSErr PipeStartNextSession(
unsigned char *portName, /* --> port name of the next filter */
unsigned char id /* --> filter ID */
) {
OSErr retVal;
PPCStartPBRec startPB;
PPCPortRec portRec;
Boolean userCancelled;
/* initialise the port name structure with the name of the next filter */
portRec.nameScript = *(short *)*(GetString(pipeScriptStrNum));
StringCopy_M(portRec.name, portName);
portRec.portKindSelector = ppcByString;
StringCopy_M(portRec.u.portTypeStr, pipeType);
/* initialise the parameter block to start the session */
startPB.ioCompletion = nil;
startPB.portRefNum = port;
startPB.serviceType = ppcServiceRealTime;
startPB.resFlag = 0;
startPB.portName = &portRec;
startPB.locationName = nil;
startPB.userRefNum = 0;
startPB.userData = id;
/* try to start the session until successful or until the user cancels */
do {
retVal = PPCStart(&startPB, false);
if (retVal != noErr && PipeCheckQuit() == true) retVal = userCanceledErr;
} while (retVal != noErr && retVal != userCanceledErr);
/* if it was ok, save the session reference number */
if (retVal == noErr) {
pipeOut.session = startPB.sessRefNum;
retVal = StartOneSession(&pipeOut);
}
return (retVal);
} /* PipeStartNextSession */
/********************************************************************************* PipeStartWrite */
onlyMDOS OSErr PipeStartWrite(pipe_t *pip, long userData, unsigned char *buff) {
OSErr retVal;
Boolean userCancelled;
PPCWritePBRec *writePB;
writePB = &pip->writePB;
/*
* Before writing the message to the pipe, check that there is no previous write operation
* outstanding. If there is one, wait that it completes before proceeding.
*/
PipeWaitOnCond_M(writePB->ioResult == pipePpcNotDone, userCancelled);
if (userCancelled) {
retVal = userCanceledErr;
}
else {
/* complete the parameter block (partially initialised in PipeStartOne */
StringCopy_M(pip->writeBuff, buff);
writePB->bufferLength = (Size)buff[0] + 1;
writePB->userData = userData;
/* start the asynchronous write */
retVal = PPCWrite(writePB, true);
}
return (retVal);
} /* PipeStartWrite */
/*----------------------------------------------------------------------------------- ProcessMess */
static OSErr ProcessMess(pipeMessType_t messType, unsigned char *buff) {
OSErr retVal;
switch (messType) {
case pipeParmMess: /* Your parameters. Process them. */
if (processParms == nil)
retVal = noErr;
else
retVal = (*processParms)(buff);
break;
case pipeNextMess: /* Port name of the next filter. Start a session with it. */
nextFilterID = buff[buff[0]];
buff[0]--;
retVal = PipeStartNextSession(buff, nextFilterID);
break;
case pipeDataMess: /* This data is for your filter. Do your filtering. */
retVal = pipeReturnE;
break;
default:
retVal = pipeUnknownMessErr;
break;
}
return (retVal);
} /* ProcessMess */
/*------------------------------------------------------------------------------- StartOneSession */
static OSErr StartOneSession(
pipe_t *p /* --> pipe structure */
) {
OSErr retVal;
PPCReadPBRec *readPB;
PPCWritePBRec *writePB;
/* prepare to write to the pipe */
writePB = &p->writePB;
writePB->ioCompletion = nil;
writePB->sessRefNum = p->session;
writePB->ioResult = noErr;
writePB->bufferPtr = (Ptr)p->writeBuff;
writePB->more = false;
/* do the initial polling of the pipe */
readPB = &p->readPB;
readPB->ioCompletion = nil;
readPB->sessRefNum = p->session;
readPB->bufferLength = (Size)pipeSize;
readPB->bufferPtr = (Ptr)p->readBuff;
retVal = PPCRead(readPB, true);
return (retVal);
} /* StartOneSession */